package grammar;

import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.Pair;
import geometry.Geometry;
import geometry.Transform;
import graph.GNode;
import graph.NodeType;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import constraint.Constraint;

public class ModificationRule implements Rule {
    private Design lhs;
    private Design rhs;
    private List<GNode> addNodes;
    private List<GNode> removeNodes;
    private InstantiationInput input;
    private GNode[] order;
    private Map<Geometry, String[]> labelsToRemove;
    private Map<Geometry, String[]> labelsToAdd;
    
    public ModificationRule(Design lhs, Design rhs, InstantiationInput input, GNode[] order) {
        this.lhs = lhs;
        this.rhs = rhs;
        this.input = input;
        this.order = order;
        preprocess();
    }
    
    public String toString() {
        String s = "<<LHS>>";
        s = s +lhs.toString();
        s = s + "<<RHS>>";
        s = s + rhs.toString();
        return s;
    }
    
    private void preprocess() {
        addNodes = new ArrayList<GNode>();
        removeNodes = new ArrayList<GNode>();
        
        HashSet<GNode> diff = new HashSet<GNode>(rhs.getNodes(NodeType.PRODUCT));
        diff.removeAll(lhs.getNodes(NodeType.PRODUCT));
        addNodes.addAll(diff);

        diff = new HashSet<GNode>(rhs.getNodes(NodeType.ASSEMBLY));
        diff.removeAll(lhs.getNodes(NodeType.ASSEMBLY));
        addNodes.addAll(diff);
        
        diff = new HashSet<GNode>(rhs.getNodes(NodeType.PART));
        diff.removeAll(lhs.getNodes(NodeType.PART));
        addNodes.addAll(diff);
        
        diff = new HashSet<GNode>(rhs.getNodes(NodeType.DETAIL));
        diff.removeAll(lhs.getNodes(NodeType.DETAIL));
        addNodes.addAll(diff);
    }

    public void apply(Design design, Match match, InstantiationInput input) {
        Map<GNode, GNode> newNodeMapping = new HashMap<GNode, GNode>(); // mapping of rhs nodes to the instantiated version in the design

        Transform transformation = null;
        Geometry designGeom = null;
        Geometry lhsGeom = null;
        for (GNode node : rhs.getNodes(NodeType.PART)) {
            if(lhs.containsVertex(node)) {
                transformation = match.getTransformation(node);
                lhsGeom = (Geometry) node;
                designGeom = (Geometry) match.getMatchedNode(node);
            }
        }
        
        //Iterate through all nodes and edges on the rhs. Add them to the design if they aren't on the lhs.
        if (lhs.getProduct() != rhs.getProduct()) {
            GNode productCopy = rhs.getProduct().copy();
            design.addNode(productCopy);
            newNodeMapping.put(rhs.getProduct(), productCopy);
        }
        for (GNode node : rhs.getNodes(NodeType.ASSEMBLY)) {
            if(!lhs.containsVertex(node)) {
                GNode assemblyCopy = node.copy();
                design.addNode(assemblyCopy);
                design.addEdge(design.getProduct(), assemblyCopy);
                newNodeMapping.put(node, assemblyCopy);
            }
        }
        for (GNode node : rhs.getNodes(NodeType.PART)) {
            if(!lhs.containsVertex(node)) {
                GNode partCopy = (GNode) ((Geometry)node).transformInSiblingCS(transformation, lhsGeom, designGeom);
                design.addNode(partCopy);
                GNode parent = rhs.getParent(node);
                GNode designParent = match.getMatchedNode(parent);
                if (designParent == null) {
                    designParent=newNodeMapping.get(parent);
                }
                design.addEdge(designParent, partCopy);                
                newNodeMapping.put(node, partCopy);
                
            }
        }
        
        addEdge(NodeType.PART, newNodeMapping, match, design);
        
        //Remove nodes that are on the the lhs but not the rhs.
        for (GNode node : removeNodes) {
            GNode designNode = match.getMatchedNode(node);
            design.removeNode(designNode);
        }  
        
        //Remove labels that are on the the lhs but not the rhs.
        for (Geometry geom : labelsToRemove.keySet()) {
            GNode node = (GNode) geom;
            GNode matchedNode = match.getMatchedNode(node);
            matchedNode.removeLabels(labelsToRemove.get(geom));
        }
        
        //Add labels that are on the the rhs but not the lhs.
        for (Geometry geom : labelsToAdd.keySet()) {
            GNode node = (GNode) geom;
            GNode matchedNode = newNodeMapping.get(node);
            matchedNode.addLabels(labelsToAdd.get(geom));
        }
        
        
    }
   
    private void addEdge(NodeType type, Map<GNode, GNode> newNodeMapping, Match match, Design design) {
        Graph<GNode, Constraint> graph = rhs.getGraphOfType(type);
        Collection<Constraint> edges = graph.getEdges();
        for (Constraint edge : edges) {
            Pair<GNode> endpoints = graph.getEndpoints(edge);
            GNode end1 = endpoints.getFirst();
            GNode end2 = endpoints.getSecond();
            
            GNode node1 = null;
            GNode node2 = null;
            boolean oldEdge1 = false;
            if (lhs.containsVertex(end1)) { // point was also on the lhs
                oldEdge1 = true;
                node1 = match.getMatchedNode(end1);
            } else if (newNodeMapping.keySet().contains(end1)) { // point was newly added
                node1 = newNodeMapping.get(end1);
            } else {
                System.out.println("PROBLEM WITH TRACKING RHS NODE 1");
            }
            boolean oldEdge2 = false;
            if (lhs.containsVertex(end2)) { // point was also on the lhs
                oldEdge2 = true;
                node2 = match.getMatchedNode(end2);
            } else if (newNodeMapping.keySet().contains(end2)) { // point was newly added
                node2 = newNodeMapping.get(end2);
            } else {
                System.out.println("PROBLEM WITH TRACKING RHS NODE 2");
            }
            
            if ((oldEdge1 && oldEdge2) == false) {
                design.addEdge(node1, node2);
            }
        }
    }

    public Design getLHS() {
        return lhs;
    }
    
    public Design getRHS() {
        return rhs;
    }

    public void setLabelChanges(Map<Geometry, String[]> labelsToRemove,
            Map<Geometry, String[]> labelsToAdd) {
        this.labelsToAdd = labelsToAdd;
        this.labelsToRemove = labelsToRemove;
    }
}
